//#include "stdafx.h"
#include "compress/compress.h"
#include "utility/assert.h"
#include "memory/memory.h"


extern "C"
{
	#include "quicklz.h"
};
#include "zlib.h"

/*
#ifdef _DEBUG
#pragma comment(lib,"zlibd.lib")
#else
#pragma comment(lib,"zlib.lib")
#endif
*/

//#include "utility_new.h"

using namespace std;


void hexdump(const char *buffer,size_t size);

namespace KCore
{
	class compress_type_zlib : public compress_type
	{
	public:
		size_t get_new_buffer_size(size_t size) const {
			return compressBound(size);
		}
		int compress(const char *buffer,size_t size,char* newbuffer,size_t& newsize )
		{
			//hexdump(buffer,size);
			uLong comprLen = newsize;
			int err = ::compress((Byte *)newbuffer, &comprLen, (Byte *)buffer,size);
			assert_comment1(err == 0 || err == compress_Z_BUF_ERROR,__FUNCTION__,err);
			newsize = comprLen;
			//hexdump(newbuffer,newsize);
			return err;
		}
		int uncompress(const char *buffer,size_t size,char* newbuffer,size_t& newsize)
		{
			//hexdump(buffer,size);
			uLong comprLen = newsize;
			int err = ::uncompress((Byte *)newbuffer, &comprLen, (Byte *)buffer,size);
			assert_comment1(err == 0,__FUNCTION__,err);
			
			assert_exp(comprLen == newsize);
			
			newsize = comprLen;
			//hexdump(newbuffer,newsize);
			return err;
		}
	};

	extern "C"
	{
		voidpf zlib_alloc (voidpf opaque, uInt items, uInt size){
			memory* g_memory_zlib = (memory* )opaque;
			if (g_memory_zlib)
				return ALLOC2(items*size,g_memory_zlib);
			return new char[items*size];

		}
		void   zlib_free  (voidpf opaque, voidpf address){
			memory* g_memory_zlib = (memory* )opaque;
			if (g_memory_zlib)
				FREE2((void*)address,g_memory_zlib);
			else
				delete address;
		}
	};

	class compress_type_zlib_memorypool: public compress_type
	{
	public:
		memory* m_memory_zlib;
		compress_type_zlib_memorypool(){
			m_memory_zlib = memory_init(MemoryDynamic);
		}
		virtual ~compress_type_zlib_memorypool(){
			memory_clear(m_memory_zlib);
			m_memory_zlib = 0;
		}
		size_t get_new_buffer_size(size_t size) const {
			return compressBound(size);
		}
		int compress_pool(Bytef *dest,uLongf *destLen,const Bytef *source,uLong sourceLen,int level)
		{
			z_stream stream;
			int err;

			stream.next_in = (Bytef*)source;
			stream.avail_in = (uInt)sourceLen;
#ifdef MAXSEG_64K
			/* Check for source > 64K on 16-bit machine: */
			if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR;
#endif
			stream.next_out = dest;
			stream.avail_out = (uInt)*destLen;
			if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;

			stream.zalloc = (alloc_func)zlib_alloc;
			stream.zfree = (free_func)zlib_free;
			stream.opaque = m_memory_zlib;
			//stream.zalloc = (alloc_func)0;
			//stream.zfree = (free_func)0;
			//stream.opaque = (voidpf)0;

			err = deflateInit(&stream, level);
			if (err != Z_OK) return err;

			err = deflate(&stream, Z_FINISH);
			if (err != Z_STREAM_END) {
				deflateEnd(&stream);
				return err == Z_OK ? Z_BUF_ERROR : err;
			}
			*destLen = stream.total_out;

			err = deflateEnd(&stream);
			return err;
		}
		int compress(const char *buffer,size_t size,char* newbuffer,size_t& newsize )
		{
			uLong comprLen = newsize;
			int err = compress_pool((Byte *)newbuffer, &comprLen, (Byte *)buffer,size,Z_BEST_SPEED);
			assert_comment1(err == 0 || err == compress_Z_BUF_ERROR,__FUNCTION__,err);
			newsize = comprLen;
			return err;
		}
		int uncompress_pool(Bytef *dest,uLongf *destLen,const Bytef *source,uLong sourceLen)
		{
			z_stream stream;
			int err;

			stream.next_in = (Bytef*)source;
			stream.avail_in = (uInt)sourceLen;
			/* Check for source > 64K on 16-bit machine: */
			if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR;

			stream.next_out = dest;
			stream.avail_out = (uInt)*destLen;
			if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;

			stream.zalloc = (alloc_func)zlib_alloc;
			stream.zfree = (free_func)zlib_free;
			stream.opaque = m_memory_zlib;
			//stream.zalloc = (alloc_func)0;
			//stream.zfree = (free_func)0;
			//stream.opaque = (voidpf)0;

			err = inflateInit(&stream);
			if (err != Z_OK) return err;

			err = inflate(&stream, Z_FINISH);
			if (err != Z_STREAM_END) {
				inflateEnd(&stream);
				if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
					return Z_DATA_ERROR;
				return err;
			}
			*destLen = stream.total_out;

			err = inflateEnd(&stream);
			return err;
		}
		int uncompress(const char *buffer,size_t size,char* newbuffer,size_t& newsize)
		{
			uLong comprLen = newsize;
			int err = uncompress_pool((Byte *)newbuffer, &comprLen, (Byte *)buffer,size);
			assert_comment1(err == 0,__FUNCTION__,err);
			
			#ifdef __LINUX
			#else
			assert_exp(comprLen == newsize);
			#endif
			
			newsize = comprLen;
			return err;
		}
	};

	class compress_type_nocompress: public compress_type
	{
	public:
		size_t get_new_buffer_size(size_t size) const {
			return size;
		};
		int compress(const char *buffer,size_t size,char* new_buffer,size_t& newsize){
			newsize = size;
			memcpy(new_buffer,buffer,size);
			return 0;
		};
		int uncompress(const char *buffer,size_t size,char* new_buffer,size_t& newsize){
			newsize = size;
			memcpy(new_buffer,buffer,size);
			return 0;
		}
	};

	class compress_type_quicklz : public compress_type
	{
	public:
		char* m_scratch;
		compress_type_quicklz(){
			m_scratch = new char[QLZ_SCRATCH_COMPRESS];
		}
		virtual ~compress_type_quicklz(){
			delete[] m_scratch;
			m_scratch = 0;
		}
		size_t get_new_buffer_size(size_t size) const {
			return size+400;
		}
		int compress(const char *buffer,size_t size,char* newbuffer,size_t& newsize ){
			
			#ifdef __LINUX
			#else
			assert_exp(newsize >= get_new_buffer_size(size));
			#endif
			
			if (newsize < get_new_buffer_size(size))
				return -1;
			int r = qlz_compress(buffer, newbuffer, size, m_scratch);
			
			#ifdef __LINUX
			#else
			assert_exp(r > 0);
			#endif
			
			if (r <= 0)
				return -1;
			newsize = r;
			return 0;
		}
		int uncompress(const char *buffer,size_t ,char* newbuffer,size_t& newsize){
			int r = qlz_decompress(buffer, newbuffer, m_scratch);
			assert_exp(r > 0);
			if (r <= 0)
				return -1;
			assert_exp(r == newsize);
			newsize = r;
			//if (r != newsize)
				//return -1;
			return 0;
		}
	};

	class my_compress_types : public compress_types
	{
	public:
		compress_type_quicklz			m_quicklz;
		compress_type_zlib				m_zlib; 
		compress_type_zlib_memorypool	m_zlib_memorypool; 

		compress_type& get_compress_type(compress_enum code){
			if (code == compress_quicklz)
				return m_quicklz;
			if (code == compress_zlib_memorypool)
				return m_zlib_memorypool;
			return m_zlib;
		}
		size_t get_new_buffer_size(compress_enum code,size_t size) {
			return get_compress_type(code).get_new_buffer_size(size);
		};
		int compress(compress_enum code,const char *buffer,size_t size,char* new_buffer,size_t& newsize) {
			return get_compress_type(code).compress(buffer,size,new_buffer,newsize);
		}
		int uncompress(compress_enum code,const char *buffer,size_t size,char* new_buffer,size_t& newsize) {
			return get_compress_type(code).uncompress(buffer,size,new_buffer,newsize);
		}		
	};

	compress_type* create_compress_type(compress_enum e)
	{
		switch(e)
		{
		case compress_quicklz:
			return new compress_type_quicklz;
		case compress_zlib_memorypool:
			return new compress_type_zlib_memorypool;
		case compress_zlib:
			return new compress_type_zlib;
		case compress_nocompress:
			return new compress_type_nocompress;
		}
		return 0;
	}

	bool delete_compress_type(compress_type* p)
	{
		compress_type_quicklz* quicklz = dynamic_cast<compress_type_quicklz*>(p);
		if (quicklz)
		{
			delete quicklz;
			return true;
		}
		compress_type_zlib_memorypool* zlib_memorypool = dynamic_cast<compress_type_zlib_memorypool*>(p);
		if (zlib_memorypool)
		{
			delete zlib_memorypool;
			return true;
		}
		compress_type_zlib* zlib = dynamic_cast<compress_type_zlib*>(p);
		if (zlib)
		{
			delete zlib;
			return true;
		}	
		return false;
	}


	compress_types* get_compress_types()
	{
		static my_compress_types s_types;
		return &s_types;
	}

	size_t get_new_buffer_size(compress_enum code,size_t size)
	{
		compress_types* types = get_compress_types();
		return types->get_new_buffer_size(code,size);
	}
	int compress(compress_enum code,const char *buffer,size_t size,char* new_buffer,size_t& newsize) 
	{
		compress_types* types = get_compress_types();
		return types->compress(code,buffer,size,new_buffer,newsize);
	}
	int uncompress(compress_enum code,const char *buffer,size_t size,char* new_buffer,size_t& newsize) 
	{
		compress_types* types = get_compress_types();
		return types->uncompress(code,buffer,size,new_buffer,newsize);
	}
};


